/*
 * Copyright 2000-2005 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jetbrains.idea.accurev.accurev.connections;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.process.InterruptibleProcess;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.util.EnvironmentUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.idea.accurev.CancelActionException;
import org.jetbrains.idea.accurev.StreamGobbler;
import org.jetbrains.idea.accurev.accurev.ExecResult;
import org.jetbrains.idea.accurev.accurev.AccuRevSettings;
import org.jetbrains.idea.accurev.accurev.PerforceTimeoutException;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

class P4CommandLineConnection {

  private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.accurev.accurev.connections.P4CommandLineConnection");

  private P4CommandLineConnection() {
  }

  public static void runP4Command(AccuRevSettings settings,
                                  String[] p4args,
                                  ExecResult retVal,
                                  final StringBuffer inputStream,
                                  final File cwd)
    throws VcsException, PerforceTimeoutException, IOException, InterruptedException {
    executeP4CommandLine(settings, p4args, retVal, inputStream, cwd);
  }

  static private ExecResult executeP4CommandLine(final AccuRevSettings settings,
                                                              @NonNls final String[] p4args,
                                                              ExecResult retVal,
                                                              StringBuffer inputData, final File cwd) {
    try {
      runCmdLine(settings, p4args, retVal, inputData, cwd);
    }
    catch (CancelActionException e) {
      throw e;
    }
    catch (Exception e) {
      retVal.setException(e);
    }

    return retVal;
  }

  private static void runCmdLine(final AccuRevSettings settings,
                                 @NonNls final String[] p4args,
                                 final ExecResult retVal,
                                 StringBuffer inputData,
                                 File cwd)
    throws IOException,
           InterruptedException, PerforceTimeoutException {
    final String[] connArgs = settings.getConnectArgs();
    final String[] cmd = new String[1 + connArgs.length + p4args.length];
    int c = 0;
    cmd[c++] = settings.getPathToExec();
    for (String connArg : connArgs) {
      cmd[c++] = connArg;
    }
    for (String p4arg : p4args) {
      cmd[c++] = p4arg;
    }
    final StringBuffer cmdLabel = new StringBuffer();
    for (String aCmd : cmd) {
      cmdLabel.append(" ");
      cmdLabel.append(aCmd);
    }

    if (LOG.isDebugEnabled()) {
      LOG.debug("[Perf Execute:] " + cmdLabel.toString() + "[cwd] " + cwd);
    }

    final Runtime rt = Runtime.getRuntime();
    String[] env = EnvironmentUtil.getEnvironment();

    // On Unix, Perforce relies on the "PWD" variable to determine its current working directory
    // for finding .p4config.  We need to make sure it matches the directory we want to use.
    // (JetBrains bugs: IDEADEV-7445, etc.)
    setEnvironmentVariable(env, "PWD", cwd.getAbsolutePath());

    final Process proc = rt.exec(cmd, env, cwd);
    if (inputData != null) {
      OutputStream outputStream = proc.getOutputStream();
      try {
        byte[] bytes = inputData.toString().getBytes();
        outputStream.write(bytes);
      }
      finally {
        outputStream.close();
      }
    }

    final boolean needStallDialog = StallConnectionUtil.needDialog();
    InterruptibleProcess worker = new InterruptibleProcess(proc, settings.SERVER_TIMEOUT, TimeUnit.MILLISECONDS) {
      protected int processTimeout() {
        return StallConnectionUtil.requestUser();
      }

      @Override
      protected int processTimeoutInEDT() {
        if (!needStallDialog || settings.getProject().isDisposed()) {
          return -2;
        }
        return super.processTimeoutInEDT();
      }
    };

    final StreamGobbler errorGobbler = new StreamGobbler(worker.getErrorStream());
    final StreamGobbler outputGobbler = new StreamGobbler(worker.getInputStream());

    final Future<?> errorStreamReadingFuture = ApplicationManager.getApplication().executeOnPooledThread(errorGobbler);
    final Future<?> outputStreamReadingFuture = ApplicationManager.getApplication().executeOnPooledThread(outputGobbler);

    final int rc = worker.execute();

    try {
      errorStreamReadingFuture.get();
      outputStreamReadingFuture.get();

      worker.getInputStream().close();
      worker.getErrorStream().close();
    }
    catch (ExecutionException e) {
      throw new RuntimeException(e.getCause());
    }

    if (rc == 0) {
      retVal.setExitCode(worker.getExitCode());
      retVal.setOutputGobbler(outputGobbler);
      retVal.setErrorGobbler(errorGobbler);
    }
    else if (rc == -2) {
      throw new PerforceTimeoutException();
    }
    else {
      if (settings.canBeChanged()) {
        settings.disable();
      }
      throw new PerforceTimeoutException();
    }
  }

  private static void setEnvironmentVariable(String[] env, @NonNls String name, String newValue) {
    for (int i = 0; i < env.length; i++) {
      String var = env[i];
      if (var.startsWith(name + "=")) {
        env[i] = name + "=" + newValue;
      }
    }
  }
}
